/**
 *    Copyright (C) 2007-2009 DIVIS GmbH (http://www.divis-hagen.de/)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 3 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General
 *    Public License along with this library; if not, write to the
 *    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *    Boston, MA 02110-1301 USA
 */

package de.divis_hagen.json;

import de.divis_hagen.json.annotation.JSONFormat;
import de.divis_hagen.json.annotation.JSTypeName;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.xml.bind.annotation.XmlRootElement;


/**
 *
 * @author Aike J Sommer
 */
public abstract class TypeMeta<Type> {

    private final boolean valid;

    public TypeMeta(boolean valid) {
        this.valid = valid;
    }

    public boolean isValid() {
        return valid;
    }

    public abstract boolean isPrimitive();
    public abstract String getName();


//    public static class Wrapper <Type> extends TypeMeta<Type> {
//
//        private TypeMeta<Type> parent;
//
//        public Wrapper(TypeMeta<Type> parent) {
//            this(parent, parent.isValid());
//            this.parent = parent;
//        }
//
//        public Wrapper(TypeMeta<Type> parent, boolean valid) {
//            super(valid);
//            this.parent = parent;
//        }
//
//        @Override
//        public boolean isPrimitive() {
//            return parent.isPrimitive();
//        }
//
//        @Override
//        public String getName() {
//            return parent.getName();
//        }
//
//    }

    private static final Map<Class, Primitive> primitives = new HashMap<Class, Primitive>();
    static {
        primitives.put(Byte.TYPE, new Primitive.Integer(Byte.TYPE));
        primitives.put(Byte.class, new Primitive.Integer(Byte.TYPE));
        primitives.put(Short.TYPE, new Primitive.Integer(Short.TYPE));
        primitives.put(Short.class, new Primitive.Integer(Short.TYPE));
        primitives.put(Integer.TYPE, new Primitive.Integer(Integer.TYPE));
        primitives.put(Integer.class, new Primitive.Integer(Integer.TYPE));
        primitives.put(Long.TYPE, new Primitive.Integer(Long.TYPE));
        primitives.put(Long.class, new Primitive.Integer(Long.TYPE));
        primitives.put(Boolean.TYPE, new Primitive.Boolean(Boolean.TYPE));
        primitives.put(Boolean.class, new Primitive.Boolean(Boolean.TYPE));
        primitives.put(String.class, new Primitive.Text(String.class));
        primitives.put(Character.TYPE, new Primitive.Text(Character.TYPE));
        primitives.put(Character.class, new Primitive.Text(Character.TYPE));
        primitives.put(Float.class, new Primitive.Float(Float.TYPE));
        primitives.put(Float.TYPE, new Primitive.Float(Float.TYPE));
        primitives.put(Double.class, new Primitive.Float(Double.TYPE));
        primitives.put(Double.TYPE, new Primitive.Float(Double.TYPE));
    }

    public static <PrimitiveType> Primitive<PrimitiveType> primitive(Class<PrimitiveType> type) {
        if (primitives.containsKey(type)) {
            return primitives.get(type);
        }
        if (type.isEnum()) {
            Primitive meta = new Primitive.Enum(type);
            primitives.put(type, meta);
            return meta;
        }
        return null;
    }

    public static abstract class Primitive<Type> extends TypeMeta<Type> {

        protected Class<Type> type;

        public Primitive(Class<Type> type) {
            super(true);
            this.type = type;
        }

        @Override
        public boolean isPrimitive() {
            return true;
        }

        @Override
        public String getName() {
            return type.getSimpleName();
        }

        public abstract Type decode(String value);

        public boolean isEnumType() {
            return type.isEnum();
        }

        public String[] getEnumValues() {
            java.lang.Enum[] values = (java.lang.Enum[]) type.getEnumConstants();
            if (values == null) {
                return new String[0];
            }

            String[] result = new String[values.length];
            for (int i = 0; i < values.length; i++) {
                java.lang.Enum value = values[i];
                result[i] = value.name();
            }
            return result;
        }

        public static class Integer<Type> extends Primitive<Type> {

            public Integer(Class<Type> type) {
                super(type);
            }

            @Override
            public Type decode(String value) {
                long l = Long.decode(value);
                return type.cast(l);
            }

        }

        public static class Float<Type> extends Primitive<Type> {

            public Float(Class<Type> type) {
                super(type);
            }

            @Override
            public Type decode(String value) {
                double d = Double.parseDouble(value);
                return type.cast(d);
            }

        }

        public static class Boolean<Type> extends Primitive<Type> {

            public Boolean(Class<Type> type) {
                super(type);
            }

            @Override
            public Type decode(String value) {
                return (Type) (java.lang.Boolean) java.lang.Boolean.parseBoolean(value);
            }

        }

        public static class Text<Type> extends Primitive<Type> {

            public Text(Class<Type> type) {
                super(type);
            }

            @Override
            public Type decode(String value) {
                return type.equals(Character.TYPE) ? (Type) (Character) value.charAt(0) : (Type) value;
            }

        }

        public static class Enum<Type> extends Primitive<Type> {

            public Enum(Class<Type> type) {
                super(type);
            }

            @Override
            public Type decode(String value) {
                return (Type) java.lang.Enum.valueOf((Class) type, value);
            }

        }

    }

    public static abstract class Complex<Type extends Object> extends TypeMeta<Type> {

        public Complex(boolean valid) {
            super(valid);
        }

        @Override
        public boolean isPrimitive() {
            return false;
        }

        public abstract String getElementName();

        public abstract boolean isRootType();

        public abstract boolean isCollectionResource();

        public abstract Type newInstance();

        public abstract Property<Type, Object, Object> getIdProperty();

        public abstract Collection<Property<Type, Object, Object>> getProperties();
        public abstract Property<Type, Object, Object> getProperty(String name);

        public abstract <KeyType, PropertyType> void addProperty(String name, TypeMeta<PropertyType> type,
                RelationType relation, Accessor<Type, KeyType, PropertyType> accessor, boolean readOnly,
                boolean id, boolean isValue, JSONFormat[] formats);

        public abstract Collection<TypeMeta> getSubTypes();

        public abstract String[] getModifiers();

    }

    public static class ComplexWrapper<Type extends Object> extends Complex<Type> {

        private TypeMeta.Complex<Type> parent;

        public ComplexWrapper(TypeMeta.Complex<Type> parent) {
            this(parent, parent.isValid());
            this.parent = parent;
        }

        public ComplexWrapper(TypeMeta.Complex<Type> parent, boolean valid) {
            super(valid);
            this.parent = parent;
        }

        @Override
        public boolean isPrimitive() {
            return parent.isPrimitive();
        }

        @Override
        public String getName() {
            return parent.getName();
        }

        @Override
        public String getElementName() {
            return parent.getElementName();
        }

        @Override
        public boolean isRootType() {
            return parent.isRootType();
        }

        @Override
        public boolean isCollectionResource() {
            return parent.isCollectionResource();
        }

        @Override
        public Type newInstance() {
            return parent.newInstance();
        }

        @Override
        public Property<Type, Object, Object> getIdProperty() {
            return parent.getIdProperty();
        }

        @Override
        public Collection<Property<Type, Object, Object>> getProperties() {
            return parent.getProperties();
        }

        @Override
        public Property<Type, Object, Object> getProperty(String name) {
            return parent.getProperty(name);
        }

        @Override
        public <KeyType, PropertyType> void addProperty(String name,
                TypeMeta<PropertyType> type, RelationType relation,
                Accessor<Type, KeyType, PropertyType> accessor, boolean readOnly, boolean id,
                boolean isValue, JSONFormat[] formats) {
            parent.addProperty(name, type, relation, accessor, readOnly, id, isValue, formats);
        }

        @Override
        public Collection<TypeMeta> getSubTypes() {
            return parent.getSubTypes();
        }

        @Override
        public String[] getModifiers() {
            return parent.getModifiers();
        }

    }

    public static class ComplexBase<Type extends Object> extends Complex<Type> {

        private final Class<Type> type;

        private final Map<String, Property<Type, Object, Object>> properties =
                new HashMap<String, Property<Type, Object, Object>>();

        private final Collection<TypeMeta> subTypes = new HashSet<TypeMeta>();

        private String idProperty;

        private final boolean rootType;

        private final boolean collectionResource;

        public ComplexBase(Class<Type> type, boolean rootType, boolean collectionResource) {
            super(!(type.isInterface()) || Modifier.isAbstract(type.getModifiers()));
            this.type = type;
            this.rootType = rootType;
            this.collectionResource = collectionResource;
        }

        @Override
        public String getName() {
            return type.isAnnotationPresent(JSTypeName.class) ?
                ((JSTypeName) type.getAnnotation(JSTypeName.class)).value()
                : type.getSimpleName();
        }

        public String getElementName() {
            return type.isAnnotationPresent(XmlRootElement.class) ?
                type.getAnnotation(XmlRootElement.class).name()
                : getName();
        }

        public boolean isRootType() {
            return rootType;
        }

        public boolean isCollectionResource() {
            return collectionResource;
        }

        public Type newInstance() {
            try {
                return type.newInstance();
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }

        public Property<Type, Object, Object> getIdProperty() {
            if (idProperty == null) {
                throw new IllegalArgumentException(type.getName());
            }
            return properties.get(idProperty);
        }

        public Collection<Property<Type, Object, Object>> getProperties() {
            return properties.values();
        }

        @Override
        public Property<Type, Object, Object> getProperty(String name) {
            return properties.get(name);
        }

        public <KeyType, PropertyType> void addProperty(String name, TypeMeta<PropertyType> type,
                RelationType relation, Accessor<Type, KeyType, PropertyType> accessor, boolean readOnly,
                boolean id, boolean isValue, JSONFormat[] formats) {
            if (id) {
                if (idProperty != null) {
                    throw new IllegalArgumentException();
                }
                idProperty = name;
            }

            Map<String, PropertyFlags> formatsMap = new HashMap<String, PropertyFlags>();
            for (JSONFormat f : formats) {
                boolean fIsValue = f.value() == DefaultableBoolean.Default ?
                    isValue : f.value() == DefaultableBoolean.True;
                boolean fReadOnly = readOnly;
                boolean fTrans = f.trans() == DefaultableBoolean.Default ?
                    false : f.trans() == DefaultableBoolean.True;
                formatsMap.put(f.format(), new PropertyFlags(fIsValue, fReadOnly, fTrans));
            }

            properties.put(name,
                    new PropertyBase<Type, Object, Object>(name, relation, (TypeMeta<Object>) type,
                    (Accessor<Type, Object, Object>) accessor,
                    new PropertyFlags(isValue, readOnly, false), formatsMap));
        }

        public Collection<TypeMeta> getSubTypes() {
            return subTypes;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ComplexBase<Type> other =
                    (ComplexBase<Type>) obj;
            if (this.type != other.type && (this.type == null || !this.type.equals(other.type))) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 97 * hash + (this.type != null ? this.type.hashCode() : 0);
            return hash;
        }

        @Override
        public String[] getModifiers() {
            return new String[0];
        }

    }


    public static interface Property<ParentType, KeyType, PropertyType> {

        String getName();
        RelationType getRelation();
        TypeMeta<PropertyType> getType();
        Accessor<ParentType, KeyType, PropertyType> getAccessor();
        PropertyFlags getFlags();
        PropertyFlags getFormat(String format);

    }

    public static class PropertyWrapper <ParentType, KeyType, PropertyType> implements
            Property<ParentType, KeyType, PropertyType> {

        private final Property<ParentType, KeyType, PropertyType> parent;

        public PropertyWrapper(Property<ParentType, KeyType, PropertyType> parent) {
            this.parent = parent;
        }

        public String getName() {
            return parent.getName();
        }

        public RelationType getRelation() {
            return parent.getRelation();
        }

        public TypeMeta<PropertyType> getType() {
            return parent.getType();
        }

        public Accessor<ParentType, KeyType, PropertyType> getAccessor() {
            return parent.getAccessor();
        }

        public PropertyFlags getFlags() {
            return parent.getFlags();
        }

        public PropertyFlags getFormat(String format) {
            return parent.getFormat(format);
        }

    }

    public static class PropertyBase<ParentType, KeyType, PropertyType> implements
            Property<ParentType, KeyType, PropertyType> {

        private final String name;

        private final RelationType relation;

        private final TypeMeta<PropertyType> type;

        private final Accessor<ParentType, KeyType, PropertyType> accessor;

//        private final Set<RequestType> transientFor;
        private final PropertyFlags flags;

        private final Map<String, PropertyFlags> formats;

//        public Property(String name, RelationType relation,
//                TypeMeta<PropertyType> type,
//                Accessor<ParentType, PropertyType> accessor) {
//            this(name, relation, type, accessor, Collections.EMPTY_SET);
//        }

        public PropertyBase(String name, RelationType relation,
                TypeMeta<PropertyType> type,
                Accessor<ParentType, KeyType, PropertyType> accessor,
                PropertyFlags flags, Map<String, PropertyFlags> formats) {
            this.name = name;
            this.relation = relation;
            this.type = type;
            this.accessor = accessor;
//            this.transientFor = transientFor.isEmpty() ? Collections.EMPTY_SET :
//                EnumSet.copyOf(transientFor);
            this.flags = flags;
            this.formats = formats;
        }

        public String getName() {
            return name;
        }

        public RelationType getRelation() {
            return relation;
        }

        public TypeMeta<PropertyType> getType() {
            return type;
        }

        public Accessor<ParentType, KeyType, PropertyType> getAccessor() {
            return accessor;
        }

        public PropertyFlags getFlags() {
            return flags;
        }

        public PropertyFlags getFormat(String format) {
            return formats.containsKey(format) ? formats.get(format) : flags;
        }

//        public boolean isTransientFor(RequestType request) {
//            return transientFor.contains(request);
//        }
//
//        public boolean isReadOnly() {
//            return readOnly;
//        }
//
//        public boolean isValue() {
//            return value;
//        }

    }

    public static interface Accessor<ParentType, KeyType, PropertyType> {

        void set(ParentType parent, KeyType key, PropertyType o);

        PropertyType get(ParentType parent, KeyType key);

        Collection<KeyType> keys(ParentType parent);

    }

    public static class PropertyFlags {

        private boolean value;
        private boolean readOnly;
        private boolean trans;

        public PropertyFlags() {
        }

        public PropertyFlags(boolean value, boolean readOnly, boolean trans) {
            this.value = value;
            this.readOnly = readOnly;
            this.trans = trans;
        }

        public boolean isValue() {
            return value;
        }

        public void setValue(boolean value) {
            this.value = value;
        }

        public boolean isReadOnly() {
            return readOnly;
        }

        public void setReadOnly(boolean readOnly) {
            this.readOnly = readOnly;
        }

        public boolean isTrans() {
            return trans;
        }

        public void setTrans(boolean trans) {
            this.trans = trans;
        }
        
    }

}
